package net.lab0.tools.evo;

import java.util.LinkedList;
import java.util.List;

import net.lab0.tools.Pair;
import net.lab0.tools.evo.iface.Elimination;
import net.lab0.tools.evo.iface.Individual;
import net.lab0.tools.evo.iface.IndividualFactory;
import net.lab0.tools.evo.iface.Selection;

/**
 * 
 * @author 116@lab0.net
 * 
 *         The population of individuals.
 * 
 * @param <TIndividual>
 *            The type of individuals this population contains.
 * @param <TSelection>
 *            The selection operator.
 */
public abstract class Population<TIndividual extends Individual<TIndividual>>
{
    protected int                    populationSize;
    /**
     * The individuals constituting the population.
     */
    protected List<TIndividual>      individuals;
    protected int                    iteration;
    
    private double                   mutationRate;
    private double                   childrenRate;
    private Selection<TIndividual>   selection;
    private Elimination<TIndividual> elimination;
    
    /**
     * 
     * @param populationSize
     *            The maximum size of this population. Must be >= 2.
     * @param mutationRate
     *            The mutation rate of this population. Must be <code>0 <= mutationRate <= 1</code>.
     * @param childrenRate
     *            The proportion of children to create. Must be <code>childrenRate<0</code>. There will be at least 1
     *            child created.
     * @param selection
     *            The individuals selection operator.
     * @param elimination
     *            The individuals elimination operator.
     * @param factory
     *            The factory to use to create new individuals. Can't be <code>null</code>.
     * 
     * @throws IllegalArgumentException
     *             In case of invalid values.
     */
    public Population(int populationSize, double mutationRate, double childrenRate, Selection<TIndividual> selection,
    Elimination<TIndividual> elimination, IndividualFactory<TIndividual> factory)
    throws IllegalArgumentException
    {
        super();
        
        // check values
        if (populationSize < 2)
        {
            throw new IllegalArgumentException("The size of the population must be at least 2");
        }
        if (mutationRate < 0.0 || mutationRate > 1.0)
        {
            throw new IllegalArgumentException("mutationRate must be between 0.0 and 1.0");
        }
        if (childrenRate <= 0.0)
        {
            throw new IllegalArgumentException("childrenRate must be greater than 0.0");
        }
        
        this.individuals = new LinkedList<>();
        for (int i = 0; i < populationSize; ++i)
        {
            individuals.add(factory.createNewIndividual());
        }
        
        this.populationSize = populationSize;
        this.mutationRate = mutationRate;
        this.selection = selection;
        this.elimination = elimination;
        this.childrenRate = childrenRate;
    }
    
    public void evolve()
    {
        while (true)
        {
            // default stop criterion
            if (stop())
            {
                break;
            }
            
            // select some individuals. create at least 1 child to have something evolving.
            int count = Math.max((int) (populationSize * childrenRate), 1);
            List<Pair<TIndividual, TIndividual>> pairs = selection.selectIndividuals(individuals, count);
            
            // add the children in the population
            for (Pair<TIndividual, TIndividual> pair : pairs)
            {
                List<TIndividual> children = pair.a.cross(pair.b);
                if (Math.random() < mutationRate)
                {
                    for (TIndividual child : children)
                    {
                        child.mutate();
                    }
                }
                individuals.addAll(children);
            }
            
            // elimination of some individuals to keep a reasonable population size
            List<TIndividual> eliminated = elimination.selectIndividuals(individuals, individuals.size()
            - populationSize);
            individuals.removeAll(eliminated);
            
            iteration++;
        }
    }
    
    public List<TIndividual> getIndividuals()
    {
        return individuals;
    }
    
    public abstract boolean stop();
}
